C/C++는 타입 제약이 엄격한 언어이다.
각 개체(변수나 상수)의 타입은 컴파일 타임에 정의되며 실행 중에 변경할 수 없다.
(Python 같은 경우, 고정된 타입이 없으며 실제로는 일부 개체를 참조하는 클래스로 구현되어 있음
겉으로 타입을 변경할 때, 다른 개체를 참조하여 새로 개체를 채택한다.)
개체(type)
- 메모리의 비트
- 해당 비트에 의미를 부여하는 타입
컴파일러는 몇몇 형변환의 경우 메모리의 비트를 서로 다르게 표시한다.
(비트를 다르게 해석하거나, 다른 접근 권한을 사용)
그 외에는 새로운 개체를 생성하여, 형을 변환한다.
C++ 형변환 연산자
- static_cast
- dynamic_cast
- const_cast
- reinterpret_cast
C에서는 "(type) 표현식” 형태의 형변환 연산자만 지원한다.
이와 같은 표현식은 단일 연산자가 연속된 변환을 촉발할 수 있다.
char a='a';
const int* ptr;
(char*)ptr=a;
위와 같은 경우, (char*) 연산자가 int->char 변환과 const->non-const 변환을 동시에 수행해야 한다.
또한 기존의 C는 형변환을 한 변수를 찾기가 어렵다.
C++의 형변환은 한 번에 한가지 타입만 변경한다.
또한 C++은 _cast 검색을 통해 형변환 변수를 검색할 수 있는 기능을 제공한다.
C++에서도 C의 형변환 연산자를 지원하지만, 사용하지 않는 것이 좋다.
기본 클래스와 파생 클래스 간의 변환C++는 기본 클래스와 파생 클래스 계층 간 정적, 동적 형변환을 제공한다.
업 캐스팅업 캐스팅(Up Casting)은 파생(자식) 클래스에서 기본(부모) 클래스로의 형변환으로
형변환의 모호함이 없으면, 항상 가능하다.
암시적으로 변환이 가능하다.
struct A{
virtual void f(void) {}
virtual ~A() {}
int ma;
};
struct B: A{ float mb; int fb(void){ return 3; }};
struct C: A {};
struct D: B, C {};
아래와 같이 base class를 인자로 하는 경우,
base class를 상속한 class들은 모두 사용가능하며, 암시적으로 형변환된다.
void f(A a){ }
void g(A& a){ }
void h(A* a){ }
B b;
f(b);
g(b);
h(&b);
D d;
A ad(B(d));
위처럼 D를 바로 A 클래스로 업 캐스팅 하는것은 불가능하다.
(D에서 B와 C의 A중 어디로 업 캐스팅 해야하는지 불명확함)
명시적으로 중간 업 캐스팅을 거치던지 아래와 같이 가상 베이스 클래스로 A를 상속해야 한다.
struct B: virtual A{}
struct C: virtual A{}
위와 같이 가상 베이스 클래스로 상속할 경우, 이를 상속함 D에는 공통의 A 멤버를 가진다.
다운 캐스팅다운 캐스팅(Down Casting)은 포인터/레퍼런스를 서브 타입의 포인터/레퍼런스로 변환
참조된 개체가 서브 타입이 아닌 경우에 대하여 동작이 정의되어 있지 않다.
다운 캐스팅은 꼭 필요한 경우가 아니면, 사용하지 않는 것이 좋음
void g(A& a){}
void h(A* a){}
B b;
g(b);
h(&b);
위와 같이 B를 A로 업캐스팅해서 사용하는 경우, B에 정의되어 있는 멤버 함수나 멤버 변수를 사용할 수 없다.
만일 a가 B의 타입의 개체를 가르킨다고 확신할 수 있다면, B&나 B*로 다운캐스팅 해서 B에 정의되어 있는
멤버 함수와 멤버 변수를 사용할 수 있다.
다운 캐스팅은 두가지 형태로 할 수 있다.
- 빠르지만, 안전하지 않은 static_cast
- 추가 비용이 들지만, 안전하며 다형성 타입에만 사용할 수 있는 dynamic_cast
static_caststatic_cast는 컴파일 타임 정보만 검사한다.
(대상 타입이 원본 타입에서 파생되었는지 여부를 검사)
void g(A& a){
B& bref=static_cast<B&>(a);
std::cout<<"fb returns "<<bref.br()<<"\n";
}
컴파일러는 B가 A의 서브클래스이고, 구현을 허용하는지만 검사한다.
g 함수의 인수 a가 B타입이 아닌 개체를 참조하는 경우, 프로그램에 동작이 정의되어 있지 않다.
(프로그램 크래시 가능)
런타임 검사를 수행하지 않기 때문에, 올바른 타입의 개체만 참조하는 것은 프로그래머의 책임이다.
(최근 컴파일러는 이와 같은 상황을 정적 분석해 잘못되 다운 캐스팅을 감지하고 경고 메시지를 보낼 수 있다.)
B* bbp=new B, *bdp=new D;
bbp=static_cast<D*>(bbp);
bdp=static_cast<D*>(bbp);
B* bxp=(argc>=1)?new B: new D;
dynamic_castdynamic_cast는 런타임에 형변화 개체가 대상 타입이나 대상의 서브 타입을 가졌는지 검사한다.
다형성 타입(하나 이상의 가상 함수를 정의하거나 상속하는 클래스)에만 적용할 수 있다.
D* dbp=dynamic_cast<D*>(bbp);
D* ddp=dynamic_cast<D*>(bdp);
dynamic_cast가 형변환을 실패할 경우, NULL 포인터를 반환한다.
레퍼런스의 잘못된 다운 캐스팅은 std::bad_cast 예외를 발생시킨다.
dynamic_cast는 내부적으로 가상 함수로 구현되어 있다. 따라서 사용자가 하나 이상의 가상 함수를 정의해
다형성 클래스를 만든 경우에만 사용가능하다.
그렇지 않으면, 모든 클래스가 가상 테이블 비용을 부담해야 한다.
When the compiler generates code it keeps around the data about the class hierachies in some
sort of table that dynamic_cast can look up later. That table can be attached to the vtable pointer
for easy lookup by the dynamic_cast implementation.
Cross castingdynamic_cast에는 참조된 개체의 타입이 B타입과 C타입의 파생 클래스인 경우,
B타입에서 C 타입으로 변환할 수 있는 기능을 제공한다.
C* cdp=dynamic_cast<C*>(bdp);
위의 예제에서 student(person을 상속) 개체에서 mathematician(person을 상속)으로 크로스 캐스팅 가능
정적 캐스팅으로는 불가능함(파생 클래스도 아니고, 기본 클래스도 아님)
간접적으로 형변환해야 한다.
cdp=static_cast<C*>(static_cast<D*>(bdp));
주소가 지정된 개체를 실제로 해당 방식으로 캐스팅할 수 있는지는 전적으로 프로그래머의 몫이다.
|
static_cast |
dynamic_cast |
클래스 |
모두 |
다형성(virtual) |
크로스 캐스팅 |
X |
O |
런타임 검사 |
X |
O |
오버헤드 |
없음 |
RTTI 검사 |